package demo.db.elasticSearch.highClient;

import com.alibaba.fastjson.JSON;
import demo.db.redis.vo.Slowlog;
import net.opentsdb.utils.DateTime;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.util.EntityUtils;
import org.apache.lucene.search.TotalHits;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.search.ClearScrollRequest;
import org.elasticsearch.action.search.ClearScrollResponse;
import org.elasticsearch.action.search.MultiSearchRequest;
import org.elasticsearch.action.search.MultiSearchResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.search.SearchScrollRequest;
import org.elasticsearch.action.search.SearchType;
import org.elasticsearch.action.support.IndicesOptions;
import org.elasticsearch.client.Request;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.Response;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.document.DocumentField;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.common.unit.Fuzziness;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.MatchQueryBuilder;
import org.elasticsearch.index.query.MultiMatchQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.rankeval.*;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.script.ScriptType;
import org.elasticsearch.script.mustache.SearchTemplateRequest;
import org.elasticsearch.search.Scroll;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.FetchSourceContext;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.FieldSortBuilder;
import org.elasticsearch.search.sort.ScoreSortBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;

/**
 * Elasticsearch 的 QueryBuilders 类用于构建各种查询。以下是一些常用的查询类型及其用法介绍。
 * <p>
 * 1. Match Query （匹配查询）匹配查询是全文搜索查询，可以对全文检索字段进行分词匹配。
 * <p>
 * searchSourceBuilder.query(QueryBuilders.matchQuery("fieldName", "searchTerm"));
 * <p>
 * 2. Term Query （精确查询）精确查询用于查找完全匹配给定值的文档。适用于关键字（keyword）类型字段。
 * <p>
 * searchSourceBuilder.query(QueryBuilders.termQuery("fieldName", "value"));
 * <p>
 * 3. Range Query （范围查询）范围查询用于查找在某个范围内的文档。可以用于数值型、日期型或字符串型字段。
 * <p>
 * searchSourceBuilder.query(QueryBuilders.rangeQuery("dateField")
 * .from("2023-01-01")
 * .to("2023-12-31"));
 * <p>
 * 4. Bool Query （布尔查询）布尔查询用于组合其他查询，可以包括 must、should、must_not 条件。
 * searchSourceBuilder.query(QueryBuilders.boolQuery()
 * .must(QueryBuilders.matchQuery("fieldName", "searchTerm"))
 * .filter(QueryBuilders.rangeQuery("dateField").from("2023-01-01").to("2023-12-31"))
 * .mustNot(QueryBuilders.termQuery("status", "inactive")));
 * <p>
 * 5. Wildcard Query （通配符查询）通配符查询用于查找与模式匹配的文档，* 表示零个或多个字符，? 表示一个字符。
 * 通配符查询基于模式匹配。你可以使用通配符字符 * 和 ? 来表示任意多个字符或单个字符
 * <p>
 * searchSourceBuilder.query(QueryBuilders.wildcardQuery("fieldName", "value*"));
 * <p>
 * 6. Prefix Query （前缀查询）前缀查询用于查找以指定前缀开头的文档。
 * searchSourceBuilder.query(QueryBuilders.prefixQuery("fieldName", "prefix"));
 * <p>
 * 7. Fuzzy Query （模糊查询） 模糊查询用于查找与指定值模糊匹配的文档。
 * 模糊查询基于编辑距离（也被称为Levenshtein距离），允许在字符串中进行少量的插入、删除或替换，使得查询字符串与目标字符串相匹配。
 * 编辑距离通常定义为两字符串之间的最小操作数。
 * searchSourceBuilder.query(QueryBuilders.fuzzyQuery("fieldName", "value").fuzziness(Fuzziness.AUTO));
 * <p>
 * 8. Exists Query （存在查询） 存在查询用于查找具有指定字段的文档。
 * searchSourceBuilder.query(QueryBuilders.existsQuery("fieldName"));
 */
public class QueryDemo {

    public static final String host = "172.17.16.165";
    public static final int port = 9200;

    private static Logger logger = LoggerFactory.getLogger(QueryDemo.class);

    RestHighLevelClient restHighLevelClient;

    @Before
    public void init() {
        restHighLevelClient = RestHighLevelClientDemo.init(RestHighLevelClientDemo.hosts);
    }

    @Test
    public void testQueryByJson() {

    }

    public String queryByJson(String index, Map<String, String> paramSource, String jsonEntity)
            throws IOException {
        Request request = new Request("GET", index + "/_search");
        request.addParameters(paramSource);
        request.setJsonEntity(jsonEntity);
        String responseBody = null;
        Response response = restHighLevelClient.getLowLevelClient()
                .performRequest(request);
        if (200 != response.getStatusLine().getStatusCode()) {
            System.err.println();
        }
        responseBody = EntityUtils.toString(response.getEntity());

        return responseBody;
    }

    /**
     * The rankEval method allows to evaluate the quality of ranked search results
     * over a set of search request. Given sets of manually rated documents for each
     * search request, ranking evaluation performs a multi search request and
     * calculates information retrieval metrics like mean reciprocal rank, precision
     * or discounted cumulative gain on the returned results.
     */
    @Test
    public void rankingEvaluationDemo() {
        EvaluationMetric metric = new PrecisionAtK();
        List<RatedDocument> ratedDocs = new ArrayList<>();
        ratedDocs.add(new RatedDocument("posts", "1", 1));
        SearchSourceBuilder searchQuery = new SearchSourceBuilder();
        searchQuery.query(QueryBuilders.matchQuery("user", "kimchy"));
        RatedRequest ratedRequest = new RatedRequest("kimchy_query", ratedDocs, searchQuery);
        List<RatedRequest> ratedRequests = Arrays.asList(ratedRequest);
        RankEvalSpec specification = new RankEvalSpec(ratedRequests, metric);
        RankEvalRequest request = new RankEvalRequest(specification, new String[]{"posts"});

        try (RestHighLevelClient client = RestHighLevelClientDemo.getClient()) {
            RankEvalResponse response = client.rankEval(request, RequestOptions.DEFAULT);
            Map<String, EvalQueryQuality> partialResults = response.getPartialResults();
            EvalQueryQuality evalQuality = partialResults.get("kimchy_query");
            assertEquals("kimchy_query", evalQuality.getId());
            List<RatedSearchHit> hitsAndRatings = evalQuality.getHitsAndRatings();
            RatedSearchHit ratedSearchHit = hitsAndRatings.get(0);
            assertEquals("2", ratedSearchHit.getSearchHit().getId());
            assertFalse(ratedSearchHit.getRating().isPresent());
            MetricDetail metricDetails = evalQuality.getMetricDetails();
            String metricName = metricDetails.getMetricName();
            assertEquals(PrecisionAtK.NAME, metricName);
            PrecisionAtK.Detail detail = (PrecisionAtK.Detail) metricDetails;
            assertEquals(1, detail.getRelevantRetrieved());
            assertEquals(3, detail.getRetrieved());
        } catch (IOException e) {
            logger.error("", e);
        }
    }

    /**
     * The search template API allows for searches to be executed from a template
     * based on the mustache language, and also for previewing rendered templates.
     */
    @Test
    public void testSearchTemplate() {
        SearchTemplateRequest request = new SearchTemplateRequest();
        request.setRequest(new SearchRequest("megacorp"));
        request.setScriptType(ScriptType.INLINE);
        request.setScript("{" + "  \"query\": { \"match\" : { \"{{field}}\" : \"{{value}}\" } },"
                + "  \"size\" : \"{{size}}\"" + "}");
        Map<String, Object> scriptParams = new HashMap<>();
        scriptParams.put("field", "title");
        scriptParams.put("value", "elasticsearch");
        scriptParams.put("size", 5);
        request.setScriptParams(scriptParams);

        try (RestHighLevelClient client = RestHighLevelClientDemo.getClient()) {
            client.searchTemplate(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            logger.error("", e);
        }
    }

    /**
     * The multiSearch API executes multiple search requests in a single http
     * request in parallel.
     */
    @Test
    public void multiSearchDemo() {
        MultiSearchRequest multiSearchRequest = new MultiSearchRequest();
        SearchRequest searchRequest1 = new SearchRequest("megacorp");
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.query(QueryBuilders.matchQuery("user", "kimchy"));
        searchRequest1.source(searchSourceBuilder);
        multiSearchRequest.add(searchRequest1);

        SearchRequest searchRequest2 = new SearchRequest("megacorp");
        searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.query(QueryBuilders.matchQuery("user", "luca"));
        searchRequest2.source(searchSourceBuilder);
        multiSearchRequest.add(searchRequest2);

        try (RestHighLevelClient client = RestHighLevelClientDemo.getClient()) {
            MultiSearchResponse multiSearchResponse = client.multiSearch(multiSearchRequest, RequestOptions.DEFAULT);
            MultiSearchResponse.Item firstResponse = multiSearchResponse.getResponses()[0];
            firstResponse.getFailure();
            SearchResponse searchResponse = firstResponse.getResponse();
            searchResponse.getHits().getTotalHits();

            MultiSearchResponse.Item secondResponse = multiSearchResponse.getResponses()[1];
            secondResponse.getFailure();
            searchResponse = secondResponse.getResponse();
            searchResponse.getHits().getTotalHits();
        } catch (IOException e) {
            logger.error("", e);
        }
    }


    /**
     * GET /megacorp/employee/_search
     */
    @Test
    public void searchAll() {
        String index = "slow_redis_index.2021-04-10";
        SearchRequest searchRequest = new SearchRequest(index);
        System.err.println(searchRequest);
        System.err.println("SearchRequest : " + JSON.toJSONString(searchRequest, true));
        try (RestHighLevelClient client = RestHighLevelClientDemo.getClient()) {
            SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
            System.err.println(searchResponse);
            System.err.println("SearchResponse : " + JSON.toJSONString(searchResponse, true));
            SearchHit[] searchHits = searchResponse.getHits().getHits();
            List<Slowlog> results = Arrays.stream(searchHits)
                    .map(hit -> JSON.parseObject(hit.getSourceAsString(), Slowlog.class)).collect(Collectors.toList());
            results.forEach(System.err::println);
            EsUtils.printSearchResponse(searchResponse);
        } catch (IOException e) {
            logger.error("", e);
        }
    }

    /**
     *
     */
    @Test
    public void searchDemo() {
        // matchAllQuery() method returns a QueryBuilder object that matches all
        // documents in the cluster:
        QueryBuilder matchAllQuery = QueryBuilders.matchAllQuery();
//        The rangeQuery() matches documents where a field's value is within a certain range:
        QueryBuilder matchDocumentsWithinRange = QueryBuilders.rangeQuery("price").from(15).to(100);
//  Providing a field name – e.g. fullName, and the corresponding value
        QueryBuilder matchSpecificFieldQuery = QueryBuilders.matchQuery("fullName", "John Doe");
//          We can as well use the multiMatchQuery() method to build a multi-fields version of the match query:
        MultiMatchQueryBuilder multiMatchQueryQuery = QueryBuilders.multiMatchQuery("Text I am looking for", "field_1",
                "field_2^3", "*_field_wildcard");

        String index = "slow_redis_index.2021-04-10";
        SearchRequest searchRequest = new SearchRequest(index);
        System.err.println(searchRequest);
        System.err.println("SearchRequest : " + JSON.toJSONString(searchRequest, true));
        try (RestHighLevelClient client = RestHighLevelClientDemo.getClient()) {
            SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
            System.err.println(searchResponse);
            System.err.println("SearchResponse : " + JSON.toJSONString(searchResponse, true));
            SearchHit[] searchHits = searchResponse.getHits().getHits();
            List<Slowlog> results = Arrays.stream(searchHits)
                    .map(hit -> JSON.parseObject(hit.getSourceAsString(), Slowlog.class)).collect(Collectors.toList());
            results.forEach(System.err::println);
            EsUtils.printSearchResponse(searchResponse);
        } catch (IOException e) {
            logger.error("", e);
        }
    }

    @Test
    public void search() {
        String index = "slow_redis_index.2021-04-10";
        SearchRequest searchRequest = new SearchRequest(index);
        SearchSourceBuilder builder = new SearchSourceBuilder()
                .postFilter(QueryBuilders.rangeQuery("executionTime").from(10.0).to(15.0));

        searchRequest.searchType(SearchType.DFS_QUERY_THEN_FETCH);
        searchRequest.source(builder);

        try (RestHighLevelClient client = RestHighLevelClientDemo.getClient()) {
            SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
            System.err.println(searchResponse);
            System.err.println("SearchResponse : " + JSON.toJSONString(searchResponse, true));
            SearchHit[] searchHits = searchResponse.getHits().getHits();
            List<Slowlog> results = Arrays.stream(searchHits)
                    .map(hit -> JSON.parseObject(hit.getSourceAsString(), Slowlog.class)).collect(Collectors.toList());
            results.forEach(System.err::println);
            EsUtils.printSearchResponse(searchResponse);
        } catch (IOException e) {
            logger.error("", e);
        }
    }

    @Test
    public void getById() {
        String index = "slow_redis_index.2021-04-10";
        String id = "zXWpuXgBB6-iknr5aWDE";
        GetRequest getRequest = new GetRequest(index);
        getRequest.id(id);
        try (RestHighLevelClient client = RestHighLevelClientDemo.getClient()) {
            GetResponse getResponse = client.get(getRequest, RequestOptions.DEFAULT);
            System.err.println(getResponse);
            System.err.println("GetResponse : " + JSON.toJSONString(getResponse, true));
            Map<String, Object> searchHits = getResponse.getSourceAsMap();
            System.err.println(searchHits);
        } catch (IOException e) {
            logger.error("", e);
        }
    }

    /**
     * 滚屏
     */
    @Test
    public void searchScroll() {
        final Scroll scroll = new Scroll(TimeValue.timeValueMinutes(1L));
        SearchRequest searchRequest = new SearchRequest("megacorp");
        searchRequest.types("employee");
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.query(QueryBuilders.matchQuery("title", "Elasticsearch"));
        searchSourceBuilder.size(30);
        searchRequest.source(searchSourceBuilder);
        searchRequest.scroll(scroll);
        try (RestHighLevelClient client = RestHighLevelClientDemo.getClient()) {
            SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
            EsUtils.printSearchResponse(searchResponse);
            String scrollId = searchResponse.getScrollId();
            SearchHits searchHits = searchResponse.getHits();
            SearchHit[] hits = searchHits.getHits();
            while (hits != null && hits.length > 0) {
                SearchScrollRequest scrollRequest = new SearchScrollRequest(scrollId);
                scrollRequest.scroll(scroll);
                searchResponse = client.searchScroll(scrollRequest, RequestOptions.DEFAULT);
                scrollId = searchResponse.getScrollId();
                hits = searchResponse.getHits().getHits();
            }
            /*
             * The search contexts used by the Search Scroll API are automatically deleted
             * when the scroll times out. But it is advised to release search contexts as
             * soon as they are not necessary anymore using the Clear Scroll API
             */
            ClearScrollRequest clearScrollRequest = new ClearScrollRequest();
            clearScrollRequest.addScrollId(scrollId);
            ClearScrollResponse clearScrollResponse = client.clearScroll(clearScrollRequest, RequestOptions.DEFAULT);
            boolean succeeded = clearScrollResponse.isSucceeded();
            int released = clearScrollResponse.getNumFreed();
        } catch (IOException e) {
            logger.error("", e);
        }
    }

    public void searchDemo2() {
        /* Without arguments this runs against all indices */
        SearchRequest searchRequest = new SearchRequest("megacorp");

        // searchRequest.routing("routing");
        /*
         * Setting IndicesOptions controls how unavailable indices are resolved and how
         * wildcard expressions are expanded
         */
        searchRequest.indicesOptions(IndicesOptions.lenientExpandOpen());
        /*
         * Use the preference parameter e.g. to execute the search to prefer local
         * shards. The default is to randomize across shards.
         */
        searchRequest.preference("_local");
        /*
         * Most search parameters are added to the SearchSourceBuilder. It offers
         * setters for everything that goes into the search request body.
         */
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

        searchSourceBuilder.query(QueryBuilders.termQuery("user", "kimchy"));
        /*
         * Set the from option that determines the result index to start searching from.
         * Defaults to 0.
         */
        searchSourceBuilder.from(0);
        /*
         * Set the size option that determines the number of search hits to return.
         * Defaults to 10.
         */
        searchSourceBuilder.size(5);
        /*
         * Set an optional timeout that controls how long the search is allowed to take.
         */
        searchSourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
        /*
         * Most search parameters are added to the SearchSourceBuilder. It offers
         * setters for everything that goes into the search request body.
         */
        searchSourceBuilder.query(QueryBuilders.matchAllQuery());

        /* Sort descending by _score (the default) */
        searchSourceBuilder.sort(new ScoreSortBuilder().order(SortOrder.DESC));
        /* Also sort ascending by _id field */
        searchSourceBuilder.sort(new FieldSortBuilder("_uid").order(SortOrder.ASC));

        /*
         * 是否返回source信息： By default, search requests return the contents of the document
         * _source but like in the Rest API you can overwrite this behavior
         */
        searchSourceBuilder.fetchSource(true);
        /* 返回指定字段 */
        String[] includeFields = new String[]{"title", "user", "innerObject.*"};
        String[] excludeFields = new String[]{"_type"};
        searchSourceBuilder.fetchSource(includeFields, excludeFields);

        searchRequest.source(searchSourceBuilder);
        searchRequest.indices("megacorp");

        try (RestHighLevelClient client = RestHighLevelClientDemo.getClient()) {
            SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
            EsUtils.printSearchResponse(searchResponse);
        } catch (IOException e) {
            logger.error("", e);
        }

    }

    public void highlighting() {
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        /* Create a field highlighter for the title field */
        HighlightBuilder.Field highlightTitle = new HighlightBuilder.Field("title");
        highlightTitle.highlighterType("unified");
        // Add the field highlighter to the highlight builder
        highlightBuilder.field(highlightTitle);
        HighlightBuilder.Field highlightUser = new HighlightBuilder.Field("user");
        highlightBuilder.field(highlightUser);

        searchSourceBuilder.highlighter(highlightBuilder);
    }

    public void demoQueryBuilder() {
        /*
         * Create a full text Match Query that matches the text "kimchy" over the field
         * "user".
         */
        MatchQueryBuilder matchQueryBuilder = new MatchQueryBuilder("user", "kimchy");
        /* Enable fuzzy matching on the match query */
        matchQueryBuilder.fuzziness(Fuzziness.AUTO);
        /* Set the prefix length option on the match query */
        matchQueryBuilder.prefixLength(3);
        /* Set the max expansion options to control the fuzzy process of the query */
        matchQueryBuilder.maxExpansions(10);

        QueryBuilder matchQueryBuilder2 = QueryBuilders.matchQuery("user", "kimchy").fuzziness(Fuzziness.AUTO)
                .prefixLength(3).maxExpansions(10);

    }

    /**
     * 高亮
     */
    @Test
    public void highlightDemo() {
        try (RestHighLevelClient client = RestHighLevelClientDemo.getClient()) {

            // 1、创建search请求
            SearchRequest searchRequest = new SearchRequest("hl_test");

            // 2、用SearchSourceBuilder来构造查询请求体 ,请仔细查看它的方法，构造各种查询的方法都在这。
            SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();

            // 构造QueryBuilder
            QueryBuilder matchQueryBuilder = QueryBuilders.matchQuery("title", "lucene solr");
            sourceBuilder.query(matchQueryBuilder);

            // 分页设置
            /*
             * sourceBuilder.from(0); sourceBuilder.size(5); ;
             */

            // 高亮设置
            HighlightBuilder highlightBuilder = new HighlightBuilder();
            highlightBuilder.requireFieldMatch(false).field("title").field("content").preTags("<strong>")
                    .postTags("</strong>");
            // 不同字段可有不同设置，如不同标签
            // HighlightBuilder.Field highlightTitle = new HighlightBuilder.Field("title");
            // highlightTitle.preTags("<strong>").postTags("</strong>");
            // highlightBuilder.field(highlightTitle);
            // HighlightBuilder.Field highlightContent = new
            // HighlightBuilder.Field("content");
            // highlightContent.preTags("<b>").postTags("</b>");
            // highlightBuilder.field(highlightContent).requireFieldMatch(false);

            sourceBuilder.highlighter(highlightBuilder);

            searchRequest.source(sourceBuilder);

            // 3、发送请求
            SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);

            // 4、处理响应
            if (RestStatus.OK.equals(searchResponse.status())) {
                // 处理搜索命中文档结果
                SearchHits hits = searchResponse.getHits();
                TotalHits totalHits = hits.getTotalHits();

                SearchHit[] searchHits = hits.getHits();
                for (SearchHit hit : searchHits) {
                    String index = hit.getIndex();
                    String type = hit.getType();
                    String id = hit.getId();
                    float score = hit.getScore();

                    // 取_source字段值
                    // String sourceAsString = hit.getSourceAsString(); //取成json串
                    Map<String, Object> sourceAsMap = hit.getSourceAsMap(); // 取成map对象
                    // 从map中取字段值
                    /*
                     * String title = (String) sourceAsMap.get("title"); String content = (String)
                     * sourceAsMap.get("content");
                     */
                    logger.info("index:" + index + "  type:" + type + "  id:" + id);
                    logger.info("sourceMap : " + sourceAsMap);
                    // 取高亮结果
                    Map<String, HighlightField> highlightFields = hit.getHighlightFields();
                    HighlightField highlight = highlightFields.get("title");
                    if (highlight != null) {
                        Text[] fragments = highlight.fragments(); // 多值的字段会有多个值
                        if (fragments != null) {
                            String fragmentString = fragments[0].string();
                            logger.info("title highlight : " + fragmentString);
                            // 可用高亮字符串替换上面sourceAsMap中的对应字段返回到上一级调用
                            // sourceAsMap.put("title", fragmentString);
                        }
                    }

                    highlight = highlightFields.get("content");
                    if (highlight != null) {
                        Text[] fragments = highlight.fragments(); // 多值的字段会有多个值
                        if (fragments != null) {
                            String fragmentString = fragments[0].string();
                            logger.info("content highlight : " + fragmentString);
                            // 可用高亮字符串替换上面sourceAsMap中的对应字段返回到上一级调用
                            // sourceAsMap.put("content", fragmentString);
                        }
                    }
                }
            }

        } catch (IOException e) {
            logger.error("", e);
        }
    }

    /**
     * 搜索数据
     */
    @Test
    public void testQueryAppLog() throws IOException {
        String appName = "alert-yun-business";
        // yyyy/MM/dd-HH:mm
        String startTime = "2024/06/20-13:41";
        String endTime = "2024/06/20-14:41";
        String ip = "alert-yun-business-559b9bc6f6-6tjzm";
        String level = "WARN";
        String logDetail = "";
        String logIdentity = "c.a.d.pool.DruidAbstractDataSource";
        String thread = "EventHandler-4-t-1";
        long start = DateTime.parseDateTimeString(startTime, null);
        long end = DateTime.parseDateTimeString(endTime, null);
        String index = "app_log" + "_" + appName;
        index = index.toLowerCase();

        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery()
                .filter(QueryBuilders.rangeQuery("timestamp").from(start).to(end));
        if (StringUtils.isNotBlank(ip)) {
            queryBuilder.must(QueryBuilders.termQuery("ip", ip));
        }
        if (StringUtils.isNotBlank(level)) {
            queryBuilder.must(QueryBuilders.termQuery("level", level));
        }
        if (StringUtils.isNotBlank(logDetail)) {
            queryBuilder.must(QueryBuilders.wildcardQuery("logDetail", logDetail));
        }
        if (StringUtils.isNotBlank(logIdentity)) {
            queryBuilder.must(QueryBuilders.termQuery("logIdentity", logIdentity));
        }
        if (StringUtils.isNotBlank(thread)) {
            queryBuilder.must(QueryBuilders.termQuery("thread", thread));
        }

        try (RestHighLevelClient client = RestHighLevelClientDemo.getClient()) {
            // 初始化ES查询
            Scroll scroll = new Scroll(TimeValue.timeValueMinutes(1L));
            SearchRequest searchRequest = new SearchRequest(index);
            SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
            searchSourceBuilder.query(queryBuilder);
            searchSourceBuilder.size(1000);
            searchRequest.source(searchSourceBuilder);
            searchRequest.scroll(scroll);

            SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
            String scrollId = searchResponse.getScrollId();
            while (true) {
                SearchHit[] searchHits = searchResponse.getHits().getHits();
                // 如果没有更多数据，跳出循环
                if (searchHits.length == 0) {
                    break;
                }

                for (SearchHit hit : searchHits) {
                    // 将每个文档写入文件，一行一个JSON对象
                    System.err.println(JSON.toJSONString(hit.getSourceAsMap()));
                }

                // 获取下一个批次
                SearchScrollRequest scrollRequest = new SearchScrollRequest(scrollId);
                scrollRequest.scroll(scroll);
                searchResponse = client.scroll(scrollRequest, RequestOptions.DEFAULT);
                scrollId = searchResponse.getScrollId();
            }
        }
    }

    @Test
    public void getDocumentDemo1() {
        try (RestHighLevelClient client = RestHighLevelClientDemo.getClient()) {
            // 1、创建获取文档请求
            GetRequest request = new GetRequest("mess", "_doc", "1");

            // 2、可选的设置
            // 选择返回的字段
            String[] includes = new String[]{"message", "*Date"};
            String[] excludes = new String[0];
            EsUtils.optionalArguments(request, null, null, null, null, null, null, true, includes, excludes);
            // 3、发送请求
            try {
                // 同步请求
                GetResponse getResponse = client.get(request, RequestOptions.DEFAULT);
                EsUtils.printGetResponse(getResponse);
            } catch (ElasticsearchException e) {
                if (e.status() == RestStatus.NOT_FOUND) {
                    logger.error("没有找到该id的文档");
                }
                if (e.status() == RestStatus.CONFLICT) {
                    logger.error("获取时版本冲突了，请在此写冲突处理逻辑！");
                }
                logger.error("获取文档异常", e);
            }
        } catch (IOException e) {
            logger.error("", e);
        }
    }

    @Test
    public void getDocumentDemo2() {
        try (RestHighLevelClient client = RestHighLevelClientDemo.getClient()) {
            // 1、创建获取文档请求
            GetRequest request = new GetRequest("mess", "_doc", "3");

            // 2、可选的设置
            String[] includes = new String[0];
            String[] excludes = new String[]{"message"};
            EsUtils.optionalArguments(request, null, null, null, null, null, null, true, includes, excludes);
            // 3、发送请求
            GetResponse getResponse = null;
            try {
                // 同步请求
                getResponse = client.get(request, RequestOptions.DEFAULT);
                EsUtils.printGetResponse(getResponse);
            } catch (ElasticsearchException e) {
                if (e.status() == RestStatus.NOT_FOUND) {
                    logger.error("没有找到该id的文档");
                }
                if (e.status() == RestStatus.CONFLICT) {
                    logger.error("获取时版本冲突了，请在此写冲突处理逻辑！");
                }
                logger.error("获取文档异常", e);
            }
        } catch (IOException e) {
            logger.error("", e);
        }
    }

    @Test
    public void getDocumentDemo3() {
        try (RestHighLevelClient client = RestHighLevelClientDemo.getClient()) {
            // 1、创建获取文档请求
            GetRequest request = new GetRequest("mess", "_doc", "3");
            request.fetchSourceContext(FetchSourceContext.FETCH_SOURCE);
            request.storedFields("message", "user");
            GetResponse getResponse = null;
            // 3、发送请求
            try {
                // 同步请求
                getResponse = client.get(request, RequestOptions.DEFAULT);
                EsUtils.printGetResponse(getResponse);
                DocumentField msgField = getResponse.getField("message");
                String message = msgField == null ? null : msgField.getValue();
                System.out.println(message);

                DocumentField userField = getResponse.getField("user");
                String user = userField == null ? null : userField.getValue();
                System.out.println(user);
            } catch (ElasticsearchException e) {
                if (e.status() == RestStatus.NOT_FOUND) {
                    logger.error("没有找到该id的文档");
                }
                if (e.status() == RestStatus.CONFLICT) {
                    logger.error("获取时版本冲突了，请在此写冲突处理逻辑！");
                }
                logger.error("获取文档异常", e);
            }
        } catch (IOException e) {
            logger.error("", e);
        }
    }

    @Test
    public void existsDocumentDemo() {
        try (RestHighLevelClient client = RestHighLevelClientDemo.getClient()) {
            // 1、创建获取文档请求
            GetRequest request = new GetRequest("mess", "_doc", "33");
            request.fetchSourceContext(FetchSourceContext.DO_NOT_FETCH_SOURCE);
            request.storedFields("_none_");
            // 3、发送请求
            // 同步请求
            boolean exists = client.exists(request, RequestOptions.DEFAULT);
            System.out.println(exists);
        } catch (IOException e) {
            logger.error("", e);
        }
    }

}
